package labox.innovation.gameserver.model.entity;

import gnu.trove.TLongHashSet;
import gnu.trove.TLongProcedure;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilderFactory;

import javolution.util.FastList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

import labox.innovation.config.Config;
import labox.innovation.gameserver.Announcements;
import labox.innovation.gameserver.datatables.NpcTemplateTable;
import labox.innovation.gameserver.instancemanager.InstanceManager;
import labox.innovation.gameserver.model.FuzeWorld;
import labox.innovation.gameserver.model.L2Object;
import labox.innovation.gameserver.model.L2Spawn;
import labox.innovation.gameserver.model.actor.L2Npc;
import labox.innovation.gameserver.model.actor.instance.FuzePcInstance;
import labox.innovation.gameserver.network.SystemMessageId;
import labox.innovation.gameserver.network.clientpackets.Say2;
import labox.innovation.gameserver.network.serverpackets.CreatureSay;
import labox.innovation.gameserver.network.serverpackets.L2GameServerPacket;
import labox.innovation.gameserver.network.serverpackets.SystemMessage;
import labox.innovation.gameserver.templates.chars.NpcTemplate;
import labox.innovation.gameserver.timer.Timer;

/** 
 * 
 * 副本对象，控制所有的player，npc，door的ID，副本时间等
 * @author evill33t, GodKratos
 * 
 */
public final class Instance {

	private final static Logger _log = LoggerFactory.getLogger(Instance.class.getName());

	/**
	 * 副本的ID
	 */
	private int _id;

	/**
	 * 副本的名称
	 */
	private String _name;

	/**
	 * 副本存储的玩家容器
	 */
	private TLongHashSet _players = new TLongHashSet();

	private final EjectPlayerProcedure _ejectProc;

	/**
	 * 副本存储的NPC容器
	 */
	private FastList<L2Npc> _npcs = new FastList<L2Npc>();

	/**
	 * 副本的出生点
	 */
	private int[] _spawnLoc = new int[3];

	/**
	 * 副本是否允许召唤
	 */
	private boolean _allowSummon = true;

	/**
	 * 副本被清空后删除的时间
	 */
	private long _emptyDestroyTime = -1;

	private long _lastLeft = -1;

	/**
	 * 副本结束的时间
	 */
	private long _instanceEndTime = -1;

	/**
	 * 是否为PVP副本
	 */
	private boolean _isPvPInstance = false;

	/**
	 * 副本的时间调度器
	 */
	protected Timer _CheckTimeUpTask;

	/**
	 * 检查副本的时间的调度定时器
	 * @author Administrator
	 *
	 */
	public final class CheckTimeUp implements Runnable {
		private int _remaining;

		public CheckTimeUp(int remaining) {
			_remaining = remaining;
		}

		public void run() {
			doCheckTimeUp(_remaining);
		}
	}

	/**
	 * 副本结束的调度定时器
	 * @author Administrator
	 *
	 */
	public class TimeUp implements Runnable {
		public void run() {
			InstanceManager.getInstance().destroyInstance(getId());
		}
	}

	/**
	 * 退出副本中的玩家操作调度器
	 * @author Administrator
	 *
	 */
	private final class EjectPlayerProcedure implements TLongProcedure {
		EjectPlayerProcedure() {
		}

		public final boolean execute(final long objId) {
			ejectPlayer(objId);
			return true;
		}
	}

	/**
	 * 发送数据包给玩家的调度器
	 * @author Administrator
	 *
	 */
	private final class SendPacketToPlayerProcedure implements TLongProcedure {
		private final L2GameServerPacket _packet;

		SendPacketToPlayerProcedure(final L2GameServerPacket packet) {
			_packet = packet;
		}

		public final boolean execute(final long objId) {
			L2Object find = FuzeWorld.getInstance().findObject(objId);
			if (!(find instanceof FuzePcInstance))
				return true;

			FuzePcInstance player = (FuzePcInstance) find;

			if (player != null && player.getInstanceId() == getId()) {
				player.sendPacket(_packet);
			}
			return true;
		}
	}

	public Instance(int id) {
		_id = id;
		_ejectProc = new EjectPlayerProcedure();//删除玩家从instance列表中处理
	}

	/**
	 *  返回这个副本的id
	 */
	public int getId() {
		return _id;
	}

	/**
	 *  返回这个副本的名称
	 */
	public String getName() {
		return _name;
	}

	public void setName(String name) {
		_name = name;
	}

	/**
	 * 副本是否允许召唤
	 */
	public boolean isSummonAllowed() {
		return _allowSummon;
	}

	/**
	 * Sets the status for the instance for summon friend type skills
	 */
	public void setAllowSummon(boolean b) {
		_allowSummon = b;
	}

	/**
	 * 返回true如果整个instance是PvP zone
	 */
	public boolean isPvPInstance() {
		return _isPvPInstance;
	}

	/**
	 * Sets PvP zone status of the instance 
	 */
	public void setPvPInstance(boolean b) {
		_isPvPInstance = b;
	}

	/**
	 * 设定instance的从现在开始结束的时间的持续性检查任务
	 * @param duration in milliseconds
	 */
	public void setDuration(int duration) {
		if (_CheckTimeUpTask != null)
			_CheckTimeUpTask.cancel();

		_CheckTimeUpTask = new Timer(500, false, new CheckTimeUp(duration));
		_CheckTimeUpTask.start();
		_instanceEndTime = System.currentTimeMillis() + duration + 500;
	}

	/**
	 * 设定副本被清空后删除的时间
	 * @param time in milliseconds
	 */
	public void setEmptyDestroyTime(long time) {
		_emptyDestroyTime = time;
	}

	/**
	 * 检查player是否存在于这个instance中
	 * @param objectId
	 * @return true if player exists in instance
	 */
	public boolean containsPlayer(long objectId) {
		return _players.contains(objectId);
	}

	/**
	 * 增加指定的player到instance中
	 * @param objectId Players object ID
	 */
	public void addPlayer(long objectId) {
		_players.add(objectId);
	}

	/**
	 * 从副本列表中删除指定的player
	 * @param objectId Players object ID
	 */
	public void removePlayer(long objectId) {
		_players.remove(objectId);

		if (_players.isEmpty() && _emptyDestroyTime >= 0) {
			_lastLeft = System.currentTimeMillis();
			setDuration((int) (_instanceEndTime - System.currentTimeMillis() - 1000));
		}
	}

	/**
	 * 通过设置副本ID为0把玩家从副本中删除，并且传送到游戏公共世界最近的城镇
	 * @param objectId
	 */
	public void ejectPlayer(long objectId) {
		FuzePcInstance player = (FuzePcInstance) FuzeWorld.getInstance().findObject(objectId);
		if (player != null && player.getInstanceId() == getId()) {
			player.setInstanceId(0);
			player.sendMessage("You were removed from the instance");
			if (getSpawnLoc()[0] != 0 && getSpawnLoc()[1] != 0 && getSpawnLoc()[2] != 0)
				player.teleToLocation(getSpawnLoc()[2], getSpawnLoc()[0], getSpawnLoc()[1]);
		}
	}

	public void addNpc(L2Npc npc) {
		_npcs.add(npc);
	}

	public void removeNpc(L2Spawn spawn) {
		_npcs.remove(spawn);
	}

	public TLongHashSet getPlayers() {
		return _players;
	}

	public FastList<L2Npc> getNpcs() {
		return _npcs;
	}

	/**
	 * Returns the spawn location for this instance to be used when leaving the instance
	 * @return int[3]
	 */
	public int[] getSpawnLoc() {
		return _spawnLoc;
	}

	/**
	 * Sets the spawn location for this instance to be used when leaving the instance
	 */
	public void setSpawnLoc(int[] loc) {
		if (loc == null || loc.length < 2)
			return;
		System.arraycopy(loc, 0, _spawnLoc, 0, 2);
	}

	public void removePlayers() {
		_players.forEach(_ejectProc);
		_players.clear();
	}

	public void removeNpcs() {
		for (L2Npc mob : _npcs) {
			if (mob != null) {
				if (mob.getSpawn() != null)
					mob.getSpawn().stopRespawn();
				mob.deleteMe();
			}
		}
		_npcs.clear();
	}

	/**
	 * 装载副本配置
	 * @param filename 模板配置的文件
	 * @throws FileNotFoundException
	 */
	public void loadInstanceTemplate(String filename) throws FileNotFoundException {
		Document doc = null;
		File xml = new File(Config.DATAPACK_ROOT, "data/instances/" + filename);

		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			factory.setValidating(false);
			factory.setIgnoringComments(true);
			doc = factory.newDocumentBuilder().parse(xml);

			for (Node n = doc.getFirstChild(); n != null; n = n.getNextSibling()) {
				if ("instance".equalsIgnoreCase(n.getNodeName())) {
					parseInstance(n);
				}
			}
		} catch (IOException e) {
			_log.error("Instance: can not find " + xml.getAbsolutePath() + " ! ", e);
		} catch (Exception e) {
			_log.error("Instance: error while loading " + xml.getAbsolutePath() + " ! ", e);
		}
	}

	/**
	 * 解析模板文件
	 * @param n
	 * @throws Exception
	 */
	private void parseInstance(Node n) throws Exception {
		L2Spawn spawnDat;
		NpcTemplate npcTemplate;
		String name = null;
		name = n.getAttributes().getNamedItem("name").getNodeValue();
		setName(name);

		Node a;
		Node first = n.getFirstChild();
		for (n = first; n != null; n = n.getNextSibling()) {
			if ("activityTime".equalsIgnoreCase(n.getNodeName())) {
				a = n.getAttributes().getNamedItem("val");
				if (a != null) {
					_CheckTimeUpTask = new Timer(15000, false, new CheckTimeUp(Integer.parseInt(a.getNodeValue()) * 60000));
					_CheckTimeUpTask.start();
					_instanceEndTime = System.currentTimeMillis() + Long.parseLong(a.getNodeValue()) * 60000 + 15000;
				}
			} else if ("allowSummon".equalsIgnoreCase(n.getNodeName())) {//是否允许召唤
				a = n.getAttributes().getNamedItem("val");
				if (a != null)
					setAllowSummon(Boolean.parseBoolean(a.getNodeValue()));
			} else if ("emptyDestroyTime".equalsIgnoreCase(n.getNodeName())) {//空instance被删除的时间
				a = n.getAttributes().getNamedItem("val");
				if (a != null)
					_emptyDestroyTime = Long.parseLong(a.getNodeValue()) * 1000;
			} else if ("PvPInstance".equalsIgnoreCase(n.getNodeName())) {//是否是PVP的instance
				a = n.getAttributes().getNamedItem("val");
				if (a != null)
					setPvPInstance(Boolean.parseBoolean(a.getNodeValue()));
			} else if ("spawnlist".equalsIgnoreCase(n.getNodeName())) {
				for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) {
					int npcId = 0, x = 0, y = 0, respawn = 0;

					if ("spawn".equalsIgnoreCase(d.getNodeName())) {

						npcId = Integer.parseInt(d.getAttributes().getNamedItem("npcId").getNodeValue());
						x = Integer.parseInt(d.getAttributes().getNamedItem("x").getNodeValue());
						y = Integer.parseInt(d.getAttributes().getNamedItem("y").getNodeValue());
						respawn = Integer.parseInt(d.getAttributes().getNamedItem("respawn").getNodeValue());

						npcTemplate = NpcTemplateTable.getInstance().getTemplate(npcId);
						if (npcTemplate != null) {
							spawnDat = new L2Spawn(npcTemplate);
							spawnDat.setLocx(x);
							spawnDat.setLocy(y);
							spawnDat.setAmount(1);
							spawnDat.setRespawnDelay(respawn);
							if (respawn == 0)
								spawnDat.stopRespawn();
							else
								spawnDat.startRespawn();
							spawnDat.setInstanceId(getId());
							spawnDat.doSpawn();
						} else {
							_log.warn("Instance: Data missing in NPC table for ID: " + npcTemplate + " in Instance " + getId());
						}
					}
				}
			} else if ("spawnpoint".equalsIgnoreCase(n.getNodeName())) {
				try {
					_spawnLoc[0] = Integer.parseInt(n.getAttributes().getNamedItem("spawnX").getNodeValue());
					_spawnLoc[1] = Integer.parseInt(n.getAttributes().getNamedItem("spawnY").getNodeValue());
					_spawnLoc[2] = Integer.parseInt(n.getAttributes().getNamedItem("spawnID").getNodeValue());
				} catch (Exception e) {
					_log.error("Error parsing instance xml: ", e);
					_spawnLoc = new int[3];
				}
			}
		}
		if (Config.DEBUG)
			_log.info(name + " Instance Template for Instance " + getId() + " loaded");
	}

	/**
	 * 检查副本的剩余的时间,时间到了会提出所有L2Character
	 * @param remaining
	 */
	protected void doCheckTimeUp(int remaining) {
		CreatureSay cs = null;
		int timeLeft;
		int interval;

		if (_players.isEmpty() && _emptyDestroyTime == 0) {
			remaining = 0;
			interval = 500;
		} else if (_players.isEmpty() && _emptyDestroyTime > 0) {
			Long emptyTimeLeft = _lastLeft + _emptyDestroyTime - System.currentTimeMillis();
			if (emptyTimeLeft <= 0) {
				interval = 0;
				remaining = 0;
			} else if (remaining > 300000 && emptyTimeLeft > 300000) {
				interval = 300000;
				remaining = remaining - 300000;
			} else if (remaining > 60000 && emptyTimeLeft > 60000) {
				interval = 60000;
				remaining = remaining - 60000;
			} else if (remaining > 30000 && emptyTimeLeft > 30000) {
				interval = 30000;
				remaining = remaining - 30000;
			} else {
				interval = 10000;
				remaining = remaining - 10000;
			}
		} else if (remaining > 300000) {
			timeLeft = remaining / 60000;
			interval = 300000;
			SystemMessage sm = new SystemMessage(SystemMessageId.DUNGEON_EXPIRES_IN_S1_MINUTES);
			sm.addString(Integer.toString(timeLeft));
			Announcements.getInstance().announceToInstance(sm, getId());
			remaining = remaining - 300000;
		} else if (remaining > 60000) {
			timeLeft = remaining / 60000;
			interval = 60000;
			SystemMessage sm = new SystemMessage(SystemMessageId.DUNGEON_EXPIRES_IN_S1_MINUTES);
			sm.addString(Integer.toString(timeLeft));
			Announcements.getInstance().announceToInstance(sm, getId());
			remaining = remaining - 60000;
		} else if (remaining > 30000) {
			timeLeft = remaining / 1000;
			interval = 30000;
			cs = new CreatureSay(0, Say2.COMMUNITY_CHANNAL, "Notice", timeLeft + " seconds left.");
			remaining = remaining - 30000;
		} else {
			timeLeft = remaining / 1000;
			interval = 10000;
			cs = new CreatureSay(0, Say2.COMMUNITY_CHANNAL, "Notice", timeLeft + " seconds left.");
			remaining = remaining - 10000;
		}
		if (cs != null)
			_players.forEach(new SendPacketToPlayerProcedure(cs));

		cancelTimer();
		if (remaining >= 10000)
			_CheckTimeUpTask = new Timer(interval, false, new CheckTimeUp(remaining));
		else
			_CheckTimeUpTask = new Timer(interval, false, new TimeUp());
		_CheckTimeUpTask.start();
	}

	public void cancelTimer() {
		if (_CheckTimeUpTask != null)
			_CheckTimeUpTask.cancel();
	}
}